Repository navigation
Consolidate edge factorization API on add_edges / qr / qr! - #363
Merged
Merged
Conversation
Migrate the two in-tree callers off `insert_linkinds` and onto `add_edge!`: - `add(tn1, tn2)` iterates the union of edges and calls `add_edge!` on each (no-op for edges already present). - `tebd` does the same once before the time-evolution loop, replacing the per-step `insert_linkinds` call. Strengthen `add_edge!` to also thread link inds when the graph edge already exists but no shared Index is present (an inconsistent state that today's API still allows). The long-term invariant is graph-edge ↔ shared-index one-to-one; until that's enforced structurally, `add_edge!` fills in whichever piece is missing. Return value follows the `Graphs.add_edge!` convention: `true` only when a new graph edge was added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the manual per-edge loops in `add(tn1, tn2)` and the tebd initial-state setup with `add_edges!`, which dispatches per-edge to the reformed `add_edge!` (ensures graph edge + link inds). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the only caller, `TreeTensorNetwork(::ITensor, ::IndsNetwork)`, to build up the vertex-tensor `Dict` directly during the post-order DFS instead of scaffolding a placeholder `ITensorNetwork` upfront. The scaffold tensors' link inds (filled in by `insert_linkinds`) never participated in the factorization — they were stripped by `setdiff(inds(a), inds(tn[dst(e)]))` and then overwritten by the `tn[src(e)] = a_l` writes anyway. The literal effective behavior was `left_inds = setdiff(inds(a), site_inds[dst(e)])`, which is what the new code computes directly. With the IndsNetwork-form caller gone, the function (and its `experimental_methods.md` entry) follow. This does not change the algorithm's observable behavior — including the existing quirk that the resulting TTN does not put each `site_inds[v]` on its own vertex (the first vertex processed in post-order absorbs every site ind that isn't `dst(e)`'s, etc.). That structural issue is real but out of scope here; tracked separately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The constructor had no in-src callers and only two test-only callers (`test/test_ttns.jl`'s 'Construct TTN from ITensor or Array' and `test/test_ttno.jl`'s 'Construct TTN operator from ITensor or Array'), each of which only verified that `contract(ttn) ≈ original_dense`. Tracing the algorithm showed it was producing factorizations that contract back to the original but do not lay out site indices on their designated vertices — the first vertex processed in post-order absorbs every site index that isn't `dst(e)`'s, leaving later vertices without their own site indices. The round-trip tests passed despite this because the misplaced site indices and trivial links still contract correctly. Drop the constructor and its two test callers rather than rewrite the algorithm: nothing outside the tests was relying on it, and a correct replacement can be designed if a real use case appears. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… ctor The 'From a dense ITensor' section documented `TreeTensorNetwork(::ITensor, ::IndsNetwork)`, which was deleted in f704f66. The `@example` block would have crashed the doc build.
`add(tn1, tn2)` and `tebd` were both calling `add_edges!` on local copies (already returned by `combine_linkinds` or a preceding `copy(ψ)`). Use `add_edges` (non-mutating) directly: same semantics, clearer functional style, and the explicit `copy(ψ)` in `tebd` is no longer needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The only caller — the multi-edge collapse step inside `add(tn1, tn2)` — is replaced by an inline loop calling `factorize_edge!(qr, tn, e)` on multi-edges. `factorize_edge!(qr, ...)` produces the same single-shared- Index end state as `combine_linkinds`, plus a free rank truncation to the actual rank of `tn[src]` on the (left-inds | multi-link) bipartition. With no callers left, drop `combine_linkinds`, `linkinds_combiners`, and `is_multi_edge`, and remove the `combine_linkinds` entries from `docs/src/experimental_methods.md`. The `combiner` import in `abstractitensornetwork.jl` is no longer needed either. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #363 +/- ##
===========================================
+ Coverage 49.96% 74.92% +24.96%
===========================================
Files 61 63 +2
Lines 2852 2820 -32
===========================================
+ Hits 1425 2113 +688
+ Misses 1427 707 -720
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Replace the family of vertex-splitting edge factorizations (`svd(tn, edge)`, `factorize(tn, edge)`, `qr(tn, edge)`) and the internal `gauge_edge` + `factorize_edge!` helpers with a single absorbing-into-dst `qr` / `qr!` pair: - `qr!(tn, edge)` mutates `tn` in place: factorize `tn[src(edge)]` along the inds it doesn't share with `tn[dst(edge)]`, write the orthogonal factor `Q` to `src(edge)`, and absorb the residual `R` into `tn[dst(edge)]`. The vertex set is unchanged; the bond between the two endpoints is re-gauged. - `qr(tn, edge) = qr!(copy(tn), edge)` is the non-mutating wrapper. Drop the dispatch on `alg::Algorithm` from `gauge_walk`, `tree_gauge`, and `gauge`. There was only ever one implementation (`Algorithm"orthogonalize"`), so the parameter was vestigial. Public `orthogonalize` / `tree_orthogonalize` signatures are unchanged. Migrate callers: - `add_edge!` and `add(tn1, tn2)` now call `qr!` directly instead of the deleted `factorize_edge!(qr, tn, edge)`. - `gauge_walk` body now calls `qr!` directly instead of `gauge_edge`. Update `docs/src/interface_methods.md` to drop the entries for `svd(tn, edge)` and `factorize(tn, edge)` and to describe the new absorbing semantics of `qr(tn, edge)`. Delete the now-orphaned `factorize(tn, 4 => 3)` regression in `test/test_itensornetwork.jl` (it was testing the vertex-splitting behavior, asserting `nv(tn_ortho) == nv(tn) + 1`). The remaining `tree_orthogonalize` checks in the same testset cover the absorbing path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`set_vertex_data!` was auto-reconciling graph edges to match the shared
indices on a written tensor. Inline edge maintenance at the two callsites
that relied on it instead: rewrite the tensors-only `ITensorNetwork{V}`
constructor with explicit O(n²) edge inference after the writes, and
update `_add_edge!` in `test/utils.jl` to use `@preserve_graph` plus an
explicit `add_edge!` on the underlying graph.
- Drop the `_ITensorCollection` Union from the `ITensorNetwork` constructor signatures and the in-file comments referencing the long-removed function-callback ctor's dispatch loop (those constructors went away in #356). The `Vector{ITensor}` / `Dict{V, ITensor}` / `Dictionary{V, ITensor}` shapes still flow through the same body via duck-typed `keys` / `keytype` / `tensors[v]`. - Use `LinearAlgebra.qr` directly inside `qr!` (instead of `factorize(...; ortho = "left")`), and reorder the residual contraction to `R * tn[dst(edge)]` so the expression reads left-to-right as "absorb the residual into the destination tensor". Drop the now-unused `factorize` import. - Refresh the stale `gauge` entry in `docs/src/experimental_methods.md` — the `alg::Algorithm` positional and `kwargs...` were dropped from `gauge` earlier in this PR.
Plain `tn[v] = ...` writes go through `set_vertex_data!`, which needs
to keep the graph edges incident to `v` in sync with the shared indices
of the new tensor. Restore `fix_edges!` for that purpose, keyed off the
non-`@preserve_graph` path; the two callsites that now maintain the
invariant explicitly (the tensors-only `ITensorNetwork{V}` constructor
and `_add_edge!` in `test/utils.jl`) bypass `set_vertex_data!` via
`@preserve_graph` and don't pay the cost. Plan is to drop this helper
once the invariant becomes structural — e.g., once an `ITensorNetwork`
reverse Index map makes auto-reconciliation either unnecessary or a
constant-time lookup.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidate the edge-mutation and edge-factorization API surface of
AbstractITensorNetworkaround two primitives —add_edges(graph-edge + link-Index threading) andqr/qr!(absorbing-into-dst factorization) — and delete the legacy paths that used to do the same things in incompatible ways.Edge-mutation / link-threading
insert_linkinds(::AbstractITensorNetwork, edges; link_space). Migrateadd(tn1, tn2)andtebdinitial-state setup toNamedGraphs.GraphsExtensions.add_edges(tn, edges).add_edge!onAbstractITensorNetworkto fill in missing link inds when the graph edge already exists (and conversely add the graph edge when only a shared Index is present), so it's a single primitive that ensures both pieces. Return value follows theGraphs.add_edge!convention (trueonly when a new graph edge was added).insert_linkinds(::AbstractIndsNetwork, edges; link_space). Its only caller — the dense-ITensorTTN constructor — is rewritten to build the vertex-tensorDictdirectly during the post-order DFS instead of scaffolding a placeholderITensorNetworkfirst; the scaffold's link inds (filled byinsert_linkinds) never participated in the factorization and were overwritten immediately bytn[src(e)] = a_l.set_vertex_data!'sfix_edges!auto-reconciliation — the tensors-onlyITensorNetwork{V}(tensors)constructor and the_add_edge!helper intest/utils.jl— to maintain the graph-edge ↔ shared-Index invariant explicitly via@preserve_graphplus a directadd_edge!on the underlying graph.fix_edges!itself stays in place for now as the safety net for plaintn[v] = ...writes; it'll be dropped once the invariant becomes structural (e.g. via a reverse Index map onITensorNetwork).Edge factorization
qr(tn, edge)/svd(tn, edge)/factorize(tn, edge)and the internalgauge_edge+factorize_edge!helpers with a single absorbing-into-dst pair:qr!(tn, edge)(mutating) andqr(tn, edge) = qr!(copy(tn), edge)(non-mutating). Vertex set unchanged; the bond onedgeis re-gauged with the orthogonal factor onsrc(edge)and the residual absorbed intotn[dst(edge)].alg::Algorithmfromgauge_walk,tree_gauge, andgauge. There was only ever one implementation (Algorithm"orthogonalize"), so the parameter was vestigial. Publicorthogonalize/tree_orthogonalizesignatures are unchanged.Multi-edge collapse
combine_linkinds,linkinds_combiners, andis_multi_edge. The only caller — the multi-edge collapse step inadd(tn1, tn2)— is replaced by an inline loop callingqr!(tn, e)on multi-edges.qr!produces the same single-shared-Index end state ascombine_linkinds, plus a free rank truncation to the actual rank oftn[src]on the (left-inds | multi-link) bipartition.Dense-ITensor TTN constructor
TreeTensorNetwork(::ITensor, ::IndsNetwork)outright. It had no in-srccallers and its two test-only callers (test/test_ttns.jl's 'Construct TTN from ITensor or Array' andtest/test_ttno.jl's 'Construct TTN operator from ITensor or Array') only verifiedcontract(ttn) ≈ original_dense. Tracing the algorithm showed it was producing factorizations that round-trip correctly but do not lay out site indices on their designated vertices — the first vertex processed in post-order absorbed every site index that wasn'tdst(e)'s, leaving later vertices without their own site indices. Drop the constructor and its two test callers; a clean replacement algorithm is written up in the development plan if a real use case appears.Docs
ITensor" section indocs/src/tree_tensor_networks.md(its@exampleblock would have crashed the doc build), theinsert_linkinds,combine_linkinds, andlinkinds_combinersentries indocs/src/experimental_methods.md, and thesvd(tn, edge)/factorize(tn, edge)entries indocs/src/interface_methods.md. Update the remainingqr(tn, edge)entry to describe the new absorbing semantics. Update thegaugeentry indocs/src/experimental_methods.mdto drop thealg::Algorithmpositional andkwargs....Targeting the
0.22.0-DEVaccumulator (already a breaking release line).